﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using FFmpegVideoClip.Configuration;
using MediaInfoLib;
using UtilityToolsCollect.ObjectsLibrary.ProcessManage;

namespace FFmpegVideoClip.Lib
{
    public class VideoDetectTools
    {
        public enum Mode { BlackdetectPix, BlackdetectPic, Negate_BlackdetectPix, Negate_BlackdetectPic, Freezedetect, Scdet , SelectGtScene }
        public abstract class DetectToolsData : IDisposable
        {
            public readonly record struct RangeSelectData(string Start,string End,bool Enable);
            public static readonly Regex VideoDetectProgressValueRegex = new(Config.VideoDetectProgressValueExpressionValue, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
            private RangeSelectData rangeSelectData = default;
            protected List<double> Datas = new();
            protected TimeSpan VideoDuration = TimeSpan.Zero;
            protected ProcessShell? Process;
            public event Action<double>? ProgressValueChange;
            public CancellationTokenSource CancellationTokenSource { get; private set; } = new CancellationTokenSource();
            public void Cancel() { if (Process != null) { Process.Dispose(); Process = null; } CancellationTokenSource.Cancel(); }
            protected void SetVideoDuration(string FilePath)
            {
                VideoDuration = TimeSpan.Zero;
                if (rangeSelectData.Enable)
                    VideoDuration = Tools.GetStringTimeToTimeSpan(rangeSelectData.End) - Tools.GetStringTimeToTimeSpan(rangeSelectData.Start);
                else if (MediaInfoTools.IsGetDuration(FilePath, out TimeSpan Result))
                    VideoDuration = Result;
            }
            protected virtual void ReadValue(string? value)
            {
                if (string.IsNullOrWhiteSpace(value))
                    return;
                Match match = VideoDetectProgressValueRegex.Match(value);
                if (!match.Success)
                    return;
                ProgressValueChange?.Invoke(Tools.GetStringTimeToTimeSpan(match.Value) / VideoDuration * 100);
            }
            protected abstract void ProcessExecute(string? FilePath);
            protected string RangeSelectArgs => rangeSelectData.Enable ? Tools.GetStringTimeToTimeSpan(rangeSelectData.Start) == TimeSpan.Zero ? $"-t {rangeSelectData.End}" : $"-ss {rangeSelectData.Start} -to {rangeSelectData.End}" : string.Empty;
            public async Task<double[]> ExecuteAsync(string FilePath, RangeSelectData rangeSelectData) 
            {
                if (disposedValue)
                    throw new ObjectDisposedException($"当前 {this.GetType().Name} 已经释放");
                if (CancellationTokenSource.IsCancellationRequested)
                    CancellationTokenSource = new CancellationTokenSource();
                this.rangeSelectData = rangeSelectData;
                return await Task.Run(() =>
                 {
                     SetVideoDuration(FilePath);
                     if (VideoDuration == TimeSpan.Zero)
                         return [];
                     try { ProcessExecute(FilePath); }
                     catch (Exception ex) { LogManage.WriteExceptionInfo("视频检测执行异常", ex, true); }
                     if (Datas == null)
                         return [];
                     return Datas.Where(p => p > 0).Distinct().OrderBy(p => p).ToArray();
                 }, CancellationTokenSource.Token);
            }
            #region Disposed
            private bool disposedValue;
            protected virtual void OnDispose()
            {
                if (Process != null)
                {
                    if (!CancellationTokenSource.IsCancellationRequested && Process.IsRunning)
                        CancellationTokenSource.Cancel();
                }
                CancellationTokenSource.Dispose();
                Datas.Clear();
                Datas = null!;
                
            }
            private void dispose() { if (!disposedValue) { OnDispose(); disposedValue = true; } }
            ~DetectToolsData() { dispose(); }
            public void Dispose() { dispose(); GC.SuppressFinalize(this); }
            #endregion
        }

        public class BlackdetectData(double Duration, BlackdetectData.Mode ExecuteMode, double SettingValue): DetectToolsData
        {
            public enum Mode { pix_th, pic_th, Negate_pix_th, Negate_pic_th }
            public static readonly Regex BlackdetectRegex = new(Config.BlackdetectRegexExpressionValue, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
            public const double Default_Pix_th = 0.05d, Default_Pic_th = 0.99d, Default_Duration = 0.4d, MaxPic_th = 1, MaxPix_th = 255;
            public const string pixValueHelp = "亮度限制:亮度低于指定值认为是黑场";
            public const string picValueHelp = "黑像素占比:占比值大于指定值，认为是黑场";
            public const string Negate_pixValueHelp = "亮度限制:亮度低于指定值认为是白场(白场是黑场翻转，因此侦测的是黑场)";
            public const string Negate_picValueHelp = "黑像素占比:占比值大于指定值，认为是黑场(白场是黑场的翻转，因此侦测的是黑场)";
            public static double Current_Pix_th = Default_Pix_th, Current_Pic_th = Default_Pic_th, Current_Pix_Duration = Default_Duration, Current_Pic_Duration = Default_Duration;
            protected override void ProcessExecute(string? FilePath)
            {
                string ExecuteModeArg;
                if (ExecuteMode == Mode.pix_th || ExecuteMode == Mode.Negate_pix_th)
                {
                    ExecuteModeArg = "pix_th";
                    Current_Pix_Duration = Duration;
                    Current_Pix_th = SettingValue;
                }
                else
                {
                    ExecuteModeArg = "pic_th";
                    Current_Pic_Duration = Duration;
                    Current_Pic_th = SettingValue;
                }
                if (ExecuteMode == Mode.pix_th || ExecuteMode == Mode.pic_th)
                    ProcessShell.ProcessExecuteWaitForExit(Config.CoreProcessor, $"-loglevel info {RangeSelectArgs} -i \"{FilePath}\" -vf blackdetect=d={Duration}:{ExecuteModeArg}={SettingValue} -f null -", ErrorReceived: ReadValue, BeforeExecute: p => Process = p);
                else
                    ProcessShell.ProcessExecuteWaitForExit(Config.CoreProcessor, $"-loglevel info {RangeSelectArgs} -i \"{FilePath}\" -vf negate,blackdetect=d={Duration}:{ExecuteModeArg}={SettingValue} -f null -", ErrorReceived: ReadValue, BeforeExecute: p => Process = p);
            }
            protected override void ReadValue(string? value)
            {
                base.ReadValue(value);
                if (string.IsNullOrWhiteSpace(value))
                    return;
                Match match = BlackdetectRegex.Match(value);
                if (!match.Success)
                    return;
                Group g;
                g = match.Groups["start"];
                if (g != null && g.Success && double.TryParse(g.Value, out double Svalue))
                    Datas.Add(Svalue);
                g = match.Groups["end"];
                if (g != null && g.Success && double.TryParse(g.Value, out double Evalue))
                    Datas.Add(Evalue);
            }
        }
        public class FreezedetectData(double Duration, double SettingValue) : DetectToolsData
        {
            public static readonly Regex FreezedetectRegex = new(Config.FreezedetectRegexExpressionValue, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
            public const double Default_SettingValue = 0.46d, Default_Duration = 0.4d, MaxValue = 1;
            public const string ValueHelp = "帧间变化噪声容限，值越小测试命中越少";
            public static double Current_SettingValue = Default_SettingValue, Current_Duration = Default_Duration;
            protected override void ProcessExecute(string? FilePath)
            {
                Current_SettingValue = SettingValue; Current_Duration = Duration;
                ProcessShell.ProcessExecuteWaitForExit(Config.CoreProcessor, $"-loglevel info {RangeSelectArgs} -i \"{FilePath}\" -vf freezedetect=n={SettingValue}:d={Duration} -f null -", ErrorReceived: ReadValue, BeforeExecute: p => Process = p);
            }
            protected override void ReadValue(string? value)
            {
                base.ReadValue(value);
                if (string.IsNullOrWhiteSpace(value))
                    return;
                Match match = FreezedetectRegex.Match(value);
                if (!match.Success)
                    return;
                Group g;
                g = match.Groups["start"];
                if (g != null && g.Success && double.TryParse(g.Value, out double Svalue))
                    Datas.Add(Svalue);
                g = match.Groups["end"];
                if (g != null && g.Success && double.TryParse(g.Value, out double Evalue))
                    Datas.Add(Evalue);
            }
        }
        public class ScdetData(double SettingValue) : DetectToolsData
        {
            public static readonly Regex ScdetRegex = new(Config.ScdetRegexExpressionValue, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
            public const double Default_SettingValue = 30d, MaxValue = 100;
            public const string ValueHelp = "场景变化检测阈值，变化的百分比，值越小测试命中越多";
            public static double Current = Default_SettingValue;
            protected override void ProcessExecute(string? FilePath)
            {
                Current = SettingValue;
                ProcessShell.ProcessExecuteWaitForExit(Config.CoreProcessor, $"-loglevel info {RangeSelectArgs} -i \"{FilePath}\" -vf scdet=t={SettingValue} -f null -", ErrorReceived: ReadValue, BeforeExecute: p => Process = p);
            }
            protected override void ReadValue(string? value)
            {
                base.ReadValue(value);
                if (string.IsNullOrWhiteSpace(value))
                    return;
                Match match = ScdetRegex.Match(value);
                if (match.Success && double.TryParse(match.Value, out double result))
                    Datas.Add(result);
            }
        }
        public class SelectGtSceneData(double SettingValue) : DetectToolsData
        {
            public static readonly Regex ScdetRegex = new(Config.SelectGtSceneRegexExpressionValue, RegexOptions.IgnoreCase | RegexOptions.CultureInvariant | RegexOptions.Compiled);
            public const double Default_SettingValue = 0.2d, MaxValue = 1;
            public const string ValueHelp = "场景变化检测分数，高于指定值的帧被命中，值底命中越多";
            public static double Current = Default_SettingValue;
            protected override void ProcessExecute(string? FilePath)
            {
                Current = SettingValue;
                ProcessShell.ProcessExecuteWaitForExit(Config.CoreProcessor, $"-loglevel info {RangeSelectArgs} -i \"{FilePath}\" -an  -vf \"select='gt(scene,{SettingValue})',showinfo\" -f null -", ErrorReceived: ReadValue, BeforeExecute: p => Process = p);
            }
            protected override void ReadValue(string? value)
            {
                base.ReadValue(value);
                if (string.IsNullOrWhiteSpace(value))
                    return;
                Match match = ScdetRegex.Match(value);
                if (match.Success && double.TryParse(match.Value, out double result))
                    Datas.Add(result);
            }
        }

    }

}
